names(list_of_cents)
[1] "fold_change_aritmetic" "fold_change_geometric" "fold_change_quadratic" "fold_change_harmonic"  "delta_aritmetic"       "delta_geometric"       "delta_quadratic"       "delta_harmonic"       

Density plot of centralities



plot_density <- function(df, titulo ){cut = 0.01
                             df %>% gather() -> df_gathered
                              df_gathered['value'] %>% filter(abs(value) > cut) %>%
                              ggplot( aes(x=value)) +
                                    geom_density(fill="deepskyblue4", color="azure4", alpha=.8)+
                                       theme(legend.position="top") +
                                    ylab("Density") + ggtitle(titulo) +
                                    xlab("Centrality") + geom_vline(xintercept=0, linetype="dashed", color = "red",  size=.4) -> Density_plot_of_centralities
                              return(Density_plot_of_centralities)}



map2(list_of_cents, names(list_of_cents), plot_density)
$fold_change_aritmetic

$fold_change_geometric

$fold_change_quadratic

$fold_change_harmonic

$delta_aritmetic

$delta_geometric

$delta_quadratic

$delta_harmonic

#centralities <-  Results[,!is.na(as.numeric(colnames(Results)))] %>% gather()

Density_plot_of_centralities.panel <- ggarrange( NULL,Density_plot_of_centralities, nrow = 2,  heights= c(1, .4), labels = c('a','b'),
                                                 hjust = -0.1, vjust = .8)

Density_plot_of_centralities.panel
fba_list_of_cents
$fold_change_aritmetic

$fold_change_geometric

$fold_change_quadratic

$fold_change_harmonic

$delta_aritmetic

$delta_geometric

$delta_quadratic

$delta_harmonic
NA

Hierarchical clustering

library(tidyverse)
library(scales)


assign_node_type <- function(df){
data <- df  %>% rownames_to_column('ID') %>% filter(!str_detect(ID, regex('AA_|AA2', ignore_case = F)))  %>%
         mutate(`Node type` = ifelse(str_detect(Reaction, regex('\\[[a-z]A\\]',              ignore_case = T)), 'Astrocyte', NA)) %>% 
         mutate(`Node type` = ifelse(str_detect(Reaction, regex('\\[[a-z]N\\]',              ignore_case = T)), 'Neuron', `Node type`))    %>% 
         mutate(`Node type` = ifelse( str_detect(Reaction, regex('\\[[a-z]A\\]|\\[[a-z]N\\]',ignore_case = T), negate = T), 'Exchange', `Node type`)) %>%
         mutate(`Node type` = ifelse(str_detect(Reaction, regex('\\[[a-z]A\\]',ignore_case = T)) & 
                                       str_detect(Name, regex('DM_|Demand|sink',ignore_case = T)), 'Sink/Demand Astro', `Node type`))%>%
         mutate(`Node type` = ifelse(str_detect(Reaction, regex('\\[[a-z]N\\]',ignore_case = T)) & 
                                       str_detect(Name, regex('DM_|Demand|sink',ignore_case = T)), 'Sink/Demand Neuron', `Node type`))
                                    return(data)}



map(fba_list_of_cents, assign_node_type) -> data


clean_centralities <- function(df){ df %>% select(-c ("ID", "Name", "Reaction",  "Flux", "Sensitivity","Node type")  )}


map(data, clean_centralities) -> only_centralities

map(data, clean_centralities) -> only_centralities

filter_centralities <- function(df){df %>% select( matches('^har|^info|^eigen|^load|^close|^bet'))}

map(only_centralities, filter_centralities) -> only_centralities
 

get_total_centralities <- function(df){df %>% abs %>% rowSums }


map(only_centralities, get_total_centralities) -> total.abs.centrality 

get_summaryzed.optimality <- function(df){ pseudoLog10 <- function(x) { asinh(x/2)/log(10) }
                        
                                      df$Flux   %>% abs %>% as.matrix()  %>% pseudoLog10  %>% rescale(c(0,1))  -> fluxes
                                    df$Sensitivity  %>% abs %>% as.matrix()   %>% pseudoLog10  %>% rescale(c(0,1))-> sensitivities 
                                    
                                    fluxes + sensitivities  -> summaryzed.optimality
                                    return(summaryzed.optimality)
                                      
}


map( data,get_summaryzed.optimality) -> summaryzed.optimality

heatmap

library(ComplexHeatmap)

library(WGCNA)


get_correlation_matrices <-function(df){
                                 #scale_rows <- function(x){t(scale(t(x)))}
                                 df %>% transposeBigData   %>% cor(method = "kendall") -> hola
                                 return(hola)}

map(only_centralities, get_correlation_matrices)  -> my.corr.matrices



#all.equal(data$fold_change_aritmetic$`Node type` , data$delta_quadratic$`Node type`)
#all.equal(data$fold_change_harmonic$`Node type` , data$delta_geometric$`Node type`)

node_types <- data$fold_change_aritmetic$`Node type`

set.seed(1)

my.corr.matrices$fold_change_geometric -> my.corr.mat

left_annotation <- rowAnnotation(`Nodes` = node_types, col = list( `Nodes` = c(
                                    "Astrocyte"        = "deepskyblue3", 
                                    'Exchange'   = 'darkgreen',
                                    'Sink/Demand Astro'='darkgoldenrod',
                                    "Neuron"           = "brown2",
                                    'Sink/Demand Neuron'= 'blueviolet' )))

total.abs.centrality$fold_change_aritmetic  -> a_total.abs.cent
summaryzed.optimality$fold_change_aritmetic -> a_summaryzed.opti

right_annotation <-   
  rowAnnotation(gap = unit(12, "points"),Ce      = anno_barplot(bar_width = 0.01,width = unit(1.5, "cm"), border = T,a_total.abs.cent, gp = gpar(col = 'azure4')),
            Op        = anno_barplot(bar_width = 0.01,width = unit(1.5, "cm") ,border = T, a_summaryzed.opti, gp = gpar(col = 'azure4')))


ht <- Heatmap(my.corr.mat, name = "Correlation",  left_annotation =left_annotation,
                                            right_annotation=right_annotation,
                                 clustering_distance_columns  = function(m)   dist(m, method = 'euclidean'),
                                 cluster_columns              = function(x) fastcluster::hclust(dist(x), "median"),
              
                                clustering_distance_rows   = function(m)   dist(m, method = 'euclidean'),
                                 cluster_rows               = function(x) fastcluster::hclust(dist(x), "median"),
                                row_km = 2,
                                column_km = 2,
                                # row_split = paste0("cluster ", pa$clustering),
                                # column_split = paste0("cluster ", pa$clustering),
                                 border = TRUE,
                                 row_dend_width    = unit(3, "cm"),
                                 row_gap = unit(2, "mm"),
                                 column_gap = unit(2, "mm"),
                                  width = unit(10, "cm"), 
                                height = unit(10, "cm"),
                                column_title = c("Astrocytic cluster", "Neuronal cluster"),
                                column_title_gp = gpar(fontsize = 10),
                                  row_title_rot  = 0,
                                 show_column_names    = F,
                                show_row_names    = F,
                                 row_names_gp = gpar(fontsize = 8),
                                  row_title = c("Astrocytic\n cluster", "Neuronal\n cluster"),
                             
                                 row_title_gp = gpar(fontsize = 10))
my_heatmap = grid.grabExpr(draw(ht))

ht

 upper_panel <- ggarrange( Density_plot_of_centralities.panel,  my_heatmap, ncol = 2, widths = c(.4, 1), heights = c(1,.3), labels = c('','c'),
                                                 hjust = -4, vjust = .8)

upper_panel

Distribution of pairwise correlations

ggplot(quads, aes(x=Comparison, y=`Node correlation`, fill=Comparison)) + 
    geom_violin(trim=F, colour = "azure4")+  
    stat_compare_means( vjust= -1., hjust= 0, comparisons = my_comparisons, method = "wilcox.test", p.adjust.method = "bonferroni", label = "p.signif")+
   scale_fill_manual(values = alpha(c("deepskyblue3", "blueviolet", "brown2"), .8)) + 
   theme(plot.title = element_text(hjust = 0.5),legend.position="none",  axis.title.x=element_blank(),
         axis.text.y = element_text(angle = 45, hjust = 1), axis.text.x = element_text(angle = 50, hjust = 1))  + 
  #ggtitle("Neuronal self-correlations") + 
  scale_y_continuous(limits=c(-0.7, 2)) + geom_hline(yintercept=0, linetype="dashed", color = "darkred",  size=1)-> corr_comparisons

corr_comparisons

library(ProjectionBasedClustering)
Registered S3 method overwritten by 'htmlwidgets':
  method           from         
  print.htmlwidget tools:rstudio
library(PCAtools)
Loading required package: ggrepel

Attaching package: ‘PCAtools’

The following objects are masked from ‘package:stats’:

    biplot, screeplot
library(magrittr)

Attaching package: ‘magrittr’

The following object is masked from ‘package:purrr’:

    set_names

The following object is masked from ‘package:tidyr’:

    extract
p <- pca(my.corr.mat)

cbind(p$rotated$PC1, p$rotated$PC2) %>% as.matrix %>% as.data.frame-> my_pca




my_pca %>%  cbind( data$`Node type`) %>% set_colnames(c('PC1','PC2','node')) -> to.pca.scatter

summaryzed.optimality -> Op
total.abs.centrality -> Centrality
Centrality -> Ce

b <- ggplot(to.pca.scatter, aes(x = PC1, y = PC2)) 
b +   scale_color_manual(labels = c("Astro", "Exch",'Neu','Si/De Ast', 'Si/De Neu'), values =  c("deepskyblue3",'darkgreen','brown2','darkgoldenrod','blueviolet')) + 
  theme(legend.position="right", legend.box = "vertical")+ geom_point(aes(size = Op, color = node))-> pca_node_RedCosts




b <- ggplot(to.pca.scatter, aes(x = PC1, y = PC2)) 
b +   scale_color_manual(labels = c("Astro", "Exch",'Neu','Si/De Ast', 'Si/De Neu'), values =  c("deepskyblue3",'darkgreen','brown2','darkgoldenrod','blueviolet')) +
  theme(legend.position="right", legend.box = "vertical")+ geom_point(aes( size =  Ce,color = node))-> pca_node_centrality


bottom_panel <- ggarrange( corr_comparisons,pca_node_centrality, pca_node_RedCosts  , ncol = 3, widths  = c(.4,1,1) , labels = c('d','e','f'),
                                                 hjust = 0, vjust = 1)
Removed 3 rows containing non-finite values (stat_ydensity).Removed 3 rows containing non-finite values (stat_signif).Removed 26 rows containing missing values (geom_violin).

ggsave(file="panel_without_graph.png", plot=panel_without_graph, width=13, height=9, dpi = 320)

Node types for Networkx (python) attributes

library(tidyverse)
library(magrittr)

Results <- read_csv("Results.csv")

data_Networkx <- Results%>% 
         mutate(`Node_type` = ifelse(str_detect(Formula, regex('\\[[a-z]A\\]',              ignore_case = T)), 'Astrocyte', NA)) %>% 
         mutate(`Node_type` = ifelse(str_detect(Formula, regex('\\[[a-z]N\\]',              ignore_case = T)), 'Neuron', `Node_type`))    %>% 
         mutate(`Node_type` = ifelse( str_detect(Formula, regex('\\[[a-z]A\\]|\\[[a-z]N\\]',ignore_case = T), negate = T), 'Exchange', `Node_type`)) %>%
         mutate(`Node_type` = ifelse(str_detect(Formula, regex('\\[[a-z]A\\]',ignore_case = T)) & 
                                       str_detect(Name, regex('DM_|Demand|sink',ignore_case = T)), 'Sink/Demand Astro', `Node_type`))%>%
         mutate(`Node_type` = ifelse(str_detect(Formula, regex('\\[[a-z]N\\]',ignore_case = T)) & 
                                       str_detect(Name, regex('DM_|Demand|sink',ignore_case = T)), 'Sink/Demand Neuron', `Node_type`))


data_Networkx %<>% select(c(ID, Node_type))

write_csv(data_Networkx, 'data_Networkx.csv')
LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KHJlYWRyKQpsaWJyYXJ5KHJlYWR4bCkKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShocmJydGhlbWVzKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHRpZHlyKQpsaWJyYXJ5KHZpcmlkaXMpCmxpYnJhcnkoZ2dwdWJyKQoKZGZsaXN0X25hbWVzID0gYygnZm9sZF9jaGFuZ2VfYXJpdG1ldGljJywgJ2ZvbGRfY2hhbmdlX2dlb21ldHJpYycsICdmb2xkX2NoYW5nZV9xdWFkcmF0aWMnLCAnZm9sZF9jaGFuZ2VfaGFybW9uaWMnLAogICAgJ2RlbHRhX2FyaXRtZXRpYycsICdkZWx0YV9nZW9tZXRyaWMnLCAnZGVsdGFfcXVhZHJhdGljJywgJ2RlbHRhX2hhcm1vbmljJykKCnJlYWRfc2hlZXRzIDwtIGZ1bmN0aW9uKGFfc2hlZXQpe2RmIDwtIHJlYWRfZXhjZWwoJ2RlbHRhX2ZjX2NlbnRyYWxpZGFkZXMueGxzeCcsIHNoZWV0ID0gYV9zaGVldCkgJT4lIGNvbHVtbl90b19yb3duYW1lcygiLi4uMSIpCiAgICAgICAgICAgICAgICAgIHJldHVybihkZil9CgptYXAoZGZsaXN0X25hbWVzLCByZWFkX3NoZWV0cykgJT4lIHB1cnJyOjpzZXRfbmFtZXMoZGZsaXN0X25hbWVzKSAtPiBsaXN0X29mX2NlbnRzCm5hbWVzKGxpc3Rfb2ZfY2VudHMpCmBgYAoKRGVuc2l0eSBwbG90IG9mIGNlbnRyYWxpdGllcwpgYGB7ciBmaWcuaGVpZ2h0PTQsIGZpZy53aWR0aD00LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQoKCnBsb3RfZGVuc2l0eSA8LSBmdW5jdGlvbihkZiwgdGl0dWxvICl7Y3V0ID0gMC4wMQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRmICU+JSBnYXRoZXIoKSAtPiBkZl9nYXRoZXJlZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZl9nYXRoZXJlZFsndmFsdWUnXSAlPiUgZmlsdGVyKGFicyh2YWx1ZSkgPiBjdXQpICU+JQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnZ3Bsb3QoIGFlcyh4PXZhbHVlKSkgKwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnZW9tX2RlbnNpdHkoZmlsbD0iZGVlcHNreWJsdWU0IiwgY29sb3I9ImF6dXJlNCIsIGFscGhhPS44KSsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJ0b3AiKSArCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHlsYWIoIkRlbnNpdHkiKSArIGdndGl0bGUodGl0dWxvKSArCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHhsYWIoIkNlbnRyYWxpdHkiKSArIGdlb21fdmxpbmUoeGludGVyY2VwdD0wLCBsaW5ldHlwZT0iZGFzaGVkIiwgY29sb3IgPSAicmVkIiwgIHNpemU9LjQpIC0+IERlbnNpdHlfcGxvdF9vZl9jZW50cmFsaXRpZXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuKERlbnNpdHlfcGxvdF9vZl9jZW50cmFsaXRpZXMpfQoKCgptYXAyKGxpc3Rfb2ZfY2VudHMsIG5hbWVzKGxpc3Rfb2ZfY2VudHMpLCBwbG90X2RlbnNpdHkpCgojY2FsY3VsYXIgc3RkLCBrdXJ0b25pcyB5IHNrZXduZXNzLgpgYGAKCgpgYGB7ciBmaWcuaGVpZ2h0PTQsIGZpZy53aWR0aD00LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQojY2VudHJhbGl0aWVzIDwtICBSZXN1bHRzWywhaXMubmEoYXMubnVtZXJpYyhjb2xuYW1lcyhSZXN1bHRzKSkpXSAlPiUgZ2F0aGVyKCkKCkRlbnNpdHlfcGxvdF9vZl9jZW50cmFsaXRpZXMucGFuZWwgPC0gZ2dhcnJhbmdlKCBOVUxMLERlbnNpdHlfcGxvdF9vZl9jZW50cmFsaXRpZXMsIG5yb3cgPSAyLCAgaGVpZ2h0cz0gYygxLCAuNCksIGxhYmVscyA9IGMoJ2EnLCdiJyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoanVzdCA9IC0wLjEsIHZqdXN0ID0gLjgpCgpEZW5zaXR5X3Bsb3Rfb2ZfY2VudHJhbGl0aWVzLnBhbmVsCgoKYGBgCgpgYGB7cn0KCmZiYSA8LSAgcmVhZC5jc3YoJy9ob21lL2FsZWphbmRyby9OQU1VX2luX3Byb2dyZXNzL0ZpZ3VyZXNfY3JlYXRpb24vQ29kZS9SZXN1bHRzX0FjZXZlZG9fZXRfYWxfMjAyMS8wMV9waHBwc19iaXBhcnRpdGVfZmx1eGVzX3NlbnNpc19vcHRpbWFsaXR5L0ZCQV9yZXN1bHRzLmNzdicpICU+JSAKICAgICAgICBzZWxlY3QoYygnSUQnLCdOYW1lJywgJ1JlYWN0aW9uJywgJ0ZsdXgnLCAnU2Vuc2l0aXZpdHknKSkgJT4lIGNvbHVtbl90b19yb3duYW1lcygnSUQnKQoKCgphcHBlbmRfZmJhIDwtIGZ1bmN0aW9uKGNlbnQpewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhvbGEgPC0gcm93bmFtZXNfdG9fY29sdW1uKGZiYSkgICU+JSBpbm5lcl9qb2luKHJvd25hbWVzX3RvX2NvbHVtbihjZW50KSkgJT4lIGNvbHVtbl90b19yb3duYW1lcygncm93bmFtZScpCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuKGhvbGEpCn0KCgoKbWFwKGxpc3Rfb2ZfY2VudHMsIGFwcGVuZF9mYmEpIC0+IGZiYV9saXN0X29mX2NlbnRzCgoKYGBgCgpIaWVyYXJjaGljYWwgY2x1c3RlcmluZwpgYGB7cn0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoc2NhbGVzKQoKCmFzc2lnbl9ub2RlX3R5cGUgPC0gZnVuY3Rpb24oZGYpewpkYXRhIDwtIGRmICAlPiUgcm93bmFtZXNfdG9fY29sdW1uKCdJRCcpICU+JSBmaWx0ZXIoIXN0cl9kZXRlY3QoSUQsIHJlZ2V4KCdBQV98QUEyJywgaWdub3JlX2Nhc2UgPSBGKSkpICAlPiUKICAgICAgICAgbXV0YXRlKGBOb2RlIHR5cGVgID0gaWZlbHNlKHN0cl9kZXRlY3QoUmVhY3Rpb24sIHJlZ2V4KCdcXFtbYS16XUFcXF0nLCAgICAgICAgICAgICAgaWdub3JlX2Nhc2UgPSBUKSksICdBc3Ryb2N5dGUnLCBOQSkpICU+JSAKICAgICAgICAgbXV0YXRlKGBOb2RlIHR5cGVgID0gaWZlbHNlKHN0cl9kZXRlY3QoUmVhY3Rpb24sIHJlZ2V4KCdcXFtbYS16XU5cXF0nLCAgICAgICAgICAgICAgaWdub3JlX2Nhc2UgPSBUKSksICdOZXVyb24nLCBgTm9kZSB0eXBlYCkpICAgICU+JSAKICAgICAgICAgbXV0YXRlKGBOb2RlIHR5cGVgID0gaWZlbHNlKCBzdHJfZGV0ZWN0KFJlYWN0aW9uLCByZWdleCgnXFxbW2Etel1BXFxdfFxcW1thLXpdTlxcXScsaWdub3JlX2Nhc2UgPSBUKSwgbmVnYXRlID0gVCksICdFeGNoYW5nZScsIGBOb2RlIHR5cGVgKSkgJT4lCiAgICAgICAgIG11dGF0ZShgTm9kZSB0eXBlYCA9IGlmZWxzZShzdHJfZGV0ZWN0KFJlYWN0aW9uLCByZWdleCgnXFxbW2Etel1BXFxdJyxpZ25vcmVfY2FzZSA9IFQpKSAmIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdHJfZGV0ZWN0KE5hbWUsIHJlZ2V4KCdETV98RGVtYW5kfHNpbmsnLGlnbm9yZV9jYXNlID0gVCkpLCAnU2luay9EZW1hbmQgQXN0cm8nLCBgTm9kZSB0eXBlYCkpJT4lCiAgICAgICAgIG11dGF0ZShgTm9kZSB0eXBlYCA9IGlmZWxzZShzdHJfZGV0ZWN0KFJlYWN0aW9uLCByZWdleCgnXFxbW2Etel1OXFxdJyxpZ25vcmVfY2FzZSA9IFQpKSAmIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdHJfZGV0ZWN0KE5hbWUsIHJlZ2V4KCdETV98RGVtYW5kfHNpbmsnLGlnbm9yZV9jYXNlID0gVCkpLCAnU2luay9EZW1hbmQgTmV1cm9uJywgYE5vZGUgdHlwZWApKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4oZGF0YSl9CgoKCm1hcChmYmFfbGlzdF9vZl9jZW50cywgYXNzaWduX25vZGVfdHlwZSkgLT4gZGF0YQoKCmNsZWFuX2NlbnRyYWxpdGllcyA8LSBmdW5jdGlvbihkZil7IGRmICU+JSBzZWxlY3QoLWMgKCJJRCIsICJOYW1lIiwgIlJlYWN0aW9uIiwgICJGbHV4IiwgIlNlbnNpdGl2aXR5IiwiTm9kZSB0eXBlIikgICl9CgoKbWFwKGRhdGEsIGNsZWFuX2NlbnRyYWxpdGllcykgLT4gb25seV9jZW50cmFsaXRpZXMKYGBgCgoKYGBge3J9CgptYXAoZGF0YSwgY2xlYW5fY2VudHJhbGl0aWVzKSAtPiBvbmx5X2NlbnRyYWxpdGllcwoKZmlsdGVyX2NlbnRyYWxpdGllcyA8LSBmdW5jdGlvbihkZil7ZGYgJT4lIHNlbGVjdCggbWF0Y2hlcygnXmhhcnxeaW5mb3xeZWlnZW58XmxvYWR8XmNsb3NlfF5iZXQnKSl9CgptYXAob25seV9jZW50cmFsaXRpZXMsIGZpbHRlcl9jZW50cmFsaXRpZXMpIC0+IG9ubHlfY2VudHJhbGl0aWVzCgoKYGBgCgoKYGBge3J9CiAKCmdldF90b3RhbF9jZW50cmFsaXRpZXMgPC0gZnVuY3Rpb24oZGYpe2RmICU+JSBhYnMgJT4lIHJvd1N1bXMgfQoKCm1hcChvbmx5X2NlbnRyYWxpdGllcywgZ2V0X3RvdGFsX2NlbnRyYWxpdGllcykgLT4gdG90YWwuYWJzLmNlbnRyYWxpdHkgCgpnZXRfc3VtbWFyeXplZC5vcHRpbWFsaXR5IDwtIGZ1bmN0aW9uKGRmKXsgcHNldWRvTG9nMTAgPC0gZnVuY3Rpb24oeCkgeyBhc2luaCh4LzIpL2xvZygxMCkgfQogICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZiRGbHV4ICAgJT4lIGFicyAlPiUgYXMubWF0cml4KCkgICU+JSBwc2V1ZG9Mb2cxMCAgJT4lIHJlc2NhbGUoYygwLDEpKSAgLT4gZmx1eGVzCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRmJFNlbnNpdGl2aXR5ICAlPiUgYWJzICU+JSBhcy5tYXRyaXgoKSAgICU+JSBwc2V1ZG9Mb2cxMCAgJT4lIHJlc2NhbGUoYygwLDEpKS0+IHNlbnNpdGl2aXRpZXMgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmbHV4ZXMgKyBzZW5zaXRpdml0aWVzICAtPiBzdW1tYXJ5emVkLm9wdGltYWxpdHkKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuKHN1bW1hcnl6ZWQub3B0aW1hbGl0eSkKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKfQoKCm1hcCggZGF0YSxnZXRfc3VtbWFyeXplZC5vcHRpbWFsaXR5KSAtPiBzdW1tYXJ5emVkLm9wdGltYWxpdHkKCgoKCmBgYAoKaGVhdG1hcApgYGB7ciBmaWcuaGVpZ2h0PTUuNSwgZmlnLndpZHRoPTkuNSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbGlicmFyeShDb21wbGV4SGVhdG1hcCkKCmxpYnJhcnkoV0dDTkEpCgoKZ2V0X2NvcnJlbGF0aW9uX21hdHJpY2VzIDwtZnVuY3Rpb24oZGYpewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjc2NhbGVfcm93cyA8LSBmdW5jdGlvbih4KXt0KHNjYWxlKHQoeCkpKX0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGYgJT4lIHRyYW5zcG9zZUJpZ0RhdGEgICAlPiUgY29yKG1ldGhvZCA9ICJrZW5kYWxsIikgLT4gaG9sYQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4oaG9sYSl9CgptYXAob25seV9jZW50cmFsaXRpZXMsIGdldF9jb3JyZWxhdGlvbl9tYXRyaWNlcykgIC0+IG15LmNvcnIubWF0cmljZXMKCgoKI2FsbC5lcXVhbChkYXRhJGZvbGRfY2hhbmdlX2FyaXRtZXRpYyRgTm9kZSB0eXBlYCAsIGRhdGEkZGVsdGFfcXVhZHJhdGljJGBOb2RlIHR5cGVgKQojYWxsLmVxdWFsKGRhdGEkZm9sZF9jaGFuZ2VfaGFybW9uaWMkYE5vZGUgdHlwZWAgLCBkYXRhJGRlbHRhX2dlb21ldHJpYyRgTm9kZSB0eXBlYCkKCm5vZGVfdHlwZXMgPC0gZGF0YSRmb2xkX2NoYW5nZV9hcml0bWV0aWMkYE5vZGUgdHlwZWAKCgoKCgpgYGAKCgpgYGB7ciBmaWcuaGVpZ2h0PTUuNSwgZmlnLndpZHRoPTkuNSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KCnNldC5zZWVkKDEpCgpteS5jb3JyLm1hdHJpY2VzJGZvbGRfY2hhbmdlX2dlb21ldHJpYyAtPiBteS5jb3JyLm1hdAoKbGVmdF9hbm5vdGF0aW9uIDwtIHJvd0Fubm90YXRpb24oYE5vZGVzYCA9IG5vZGVfdHlwZXMsIGNvbCA9IGxpc3QoIGBOb2Rlc2AgPSBjKAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiQXN0cm9jeXRlIiAgICAgICAgPSAiZGVlcHNreWJsdWUzIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdFeGNoYW5nZScgICA9ICdkYXJrZ3JlZW4nLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnU2luay9EZW1hbmQgQXN0cm8nPSdkYXJrZ29sZGVucm9kJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIk5ldXJvbiIgICAgICAgICAgID0gImJyb3duMiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdTaW5rL0RlbWFuZCBOZXVyb24nPSAnYmx1ZXZpb2xldCcgKSkpCgp0b3RhbC5hYnMuY2VudHJhbGl0eSRmb2xkX2NoYW5nZV9hcml0bWV0aWMgIC0+IGFfdG90YWwuYWJzLmNlbnQKc3VtbWFyeXplZC5vcHRpbWFsaXR5JGZvbGRfY2hhbmdlX2FyaXRtZXRpYyAtPiBhX3N1bW1hcnl6ZWQub3B0aQoKcmlnaHRfYW5ub3RhdGlvbiA8LSAgIAogIHJvd0Fubm90YXRpb24oZ2FwID0gdW5pdCgxMiwgInBvaW50cyIpLENlICAgICAgPSBhbm5vX2JhcnBsb3QoYmFyX3dpZHRoID0gMC4wMSx3aWR0aCA9IHVuaXQoMS41LCAiY20iKSwgYm9yZGVyID0gVCxhX3RvdGFsLmFicy5jZW50LCBncCA9IGdwYXIoY29sID0gJ2F6dXJlNCcpKSwKICAgICAgICAgICAgT3AgICAgICAgID0gYW5ub19iYXJwbG90KGJhcl93aWR0aCA9IDAuMDEsd2lkdGggPSB1bml0KDEuNSwgImNtIikgLGJvcmRlciA9IFQsIGFfc3VtbWFyeXplZC5vcHRpLCBncCA9IGdwYXIoY29sID0gJ2F6dXJlNCcpKSkKCgpodCA8LSBIZWF0bWFwKG15LmNvcnIubWF0LCBuYW1lID0gIkNvcnJlbGF0aW9uIiwgIGxlZnRfYW5ub3RhdGlvbiA9bGVmdF9hbm5vdGF0aW9uLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJpZ2h0X2Fubm90YXRpb249cmlnaHRfYW5ub3RhdGlvbiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2x1c3RlcmluZ19kaXN0YW5jZV9jb2x1bW5zICA9IGZ1bmN0aW9uKG0pICAgZGlzdChtLCBtZXRob2QgPSAnZXVjbGlkZWFuJyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNsdXN0ZXJfY29sdW1ucyAgICAgICAgICAgICAgPSBmdW5jdGlvbih4KSBmYXN0Y2x1c3Rlcjo6aGNsdXN0KGRpc3QoeCksICJtZWRpYW4iKSwKICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjbHVzdGVyaW5nX2Rpc3RhbmNlX3Jvd3MgICA9IGZ1bmN0aW9uKG0pICAgZGlzdChtLCBtZXRob2QgPSAnZXVjbGlkZWFuJyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNsdXN0ZXJfcm93cyAgICAgICAgICAgICAgID0gZnVuY3Rpb24oeCkgZmFzdGNsdXN0ZXI6OmhjbHVzdChkaXN0KHgpLCAibWVkaWFuIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcm93X2ttID0gMiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2x1bW5fa20gPSAyLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgcm93X3NwbGl0ID0gcGFzdGUwKCJjbHVzdGVyICIsIHBhJGNsdXN0ZXJpbmcpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgY29sdW1uX3NwbGl0ID0gcGFzdGUwKCJjbHVzdGVyICIsIHBhJGNsdXN0ZXJpbmcpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBib3JkZXIgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByb3dfZGVuZF93aWR0aCAgICA9IHVuaXQoMywgImNtIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJvd19nYXAgPSB1bml0KDIsICJtbSIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2x1bW5fZ2FwID0gdW5pdCgyLCAibW0iKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdpZHRoID0gdW5pdCgxMCwgImNtIiksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhlaWdodCA9IHVuaXQoMTAsICJjbSIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbHVtbl90aXRsZSA9IGMoIkFzdHJvY3l0aWMgY2x1c3RlciIsICJOZXVyb25hbCBjbHVzdGVyIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sdW1uX3RpdGxlX2dwID0gZ3Bhcihmb250c2l6ZSA9IDEwKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJvd190aXRsZV9yb3QgID0gMCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2hvd19jb2x1bW5fbmFtZXMgICAgPSBGLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNob3dfcm93X25hbWVzICAgID0gRiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcm93X25hbWVzX2dwID0gZ3Bhcihmb250c2l6ZSA9IDgpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcm93X3RpdGxlID0gYygiQXN0cm9jeXRpY1xuIGNsdXN0ZXIiLCAiTmV1cm9uYWxcbiBjbHVzdGVyIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJvd190aXRsZV9ncCA9IGdwYXIoZm9udHNpemUgPSAxMCkpCm15X2hlYXRtYXAgPSBncmlkLmdyYWJFeHByKGRyYXcoaHQpKQoKaHQKYGBgCgoKCgpgYGB7ciBmaWcuaGVpZ2h0PTUuNSwgZmlnLndpZHRoPTE0fQogdXBwZXJfcGFuZWwgPC0gZ2dhcnJhbmdlKCBEZW5zaXR5X3Bsb3Rfb2ZfY2VudHJhbGl0aWVzLnBhbmVsLCAgbXlfaGVhdG1hcCwgbmNvbCA9IDIsIHdpZHRocyA9IGMoLjQsIDEpLCBoZWlnaHRzID0gYygxLC4zKSwgbGFiZWxzID0gYygnJywnYycpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaGp1c3QgPSAtNCwgdmp1c3QgPSAuOCkKCnVwcGVyX3BhbmVsCmBgYAoKRGlzdHJpYnV0aW9uIG9mIHBhaXJ3aXNlIGNvcnJlbGF0aW9ucwpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpyb3dfb3JkZXIoaHQpW1sxXV0gLT4gYXN0cm9fY2x1c3Rlcgpyb3dfb3JkZXIoaHQpW1syXV0gLT4gbmV1cm9uX2NsdXN0ZXIKZGF0YSRgTm9kZSB0eXBlYCAgIC0+IE5vZGVzCmRhdGEkSURbbmV1cm9uX2NsdXN0ZXJdICAgICAtPiBuZXVyb25fY2x1c3Rlcl9uYW1lcwpkYXRhJElEW2FzdHJvX2NsdXN0ZXJdICAgICAgLT4gYXN0cm9fY2x1c3Rlcl9uYW1lcwpodEBtYXRyaXggJT4lIGFzLmRhdGEuZnJhbWUgLT4gaGVhdG1hcF9tYXRyaXgKCmhlYXRtYXBfbWF0cml4W25ldXJvbl9jbHVzdGVyX25hbWVzLG5ldXJvbl9jbHVzdGVyX25hbWVzXSAlPiUgYXMubWF0cml4ICU+JSBjIC0+IGBOZXVyb25hbF9jbHVzdGVyYApoZWF0bWFwX21hdHJpeFthc3Ryb19jbHVzdGVyX25hbWVzLGFzdHJvX2NsdXN0ZXJfbmFtZXNdICU+JSBhcy5tYXRyaXggJT4lIGMgIC0+YEFzdHJvY3l0aWNfY2x1c3RlcmAgCmhlYXRtYXBfbWF0cml4W2FzdHJvX2NsdXN0ZXJfbmFtZXMsbmV1cm9uX2NsdXN0ZXJfbmFtZXNdICU+JSBhcy5tYXRyaXggJT4lIGMgIC0+IGBOZXVyb25fdnNfQXN0cm9jeXRlYAoKZGF0YS5mcmFtZShgTmV1cm9uYWxfY2x1c3RlcmApICU+JSBnYXRoZXIgIC0+IEEKZGF0YS5mcmFtZShgQXN0cm9jeXRpY19jbHVzdGVyYCkgJT4lIGdhdGhlciAgIC0+IEIKZGF0YS5mcmFtZSggYE5ldXJvbl92c19Bc3Ryb2N5dGVgKSAlPiUgZ2F0aGVyIC0+IEMKcXVhZHMgPC0gcmJpbmQoQSxCLEMpCmNvbG5hbWVzKHF1YWRzKSA8LSBjKCJDb21wYXJpc29uIiwiTm9kZSBjb3JyZWxhdGlvbiIpCgojcXVhZHMkQ29tcGFyaXNvbiAlPiUgdW5pcXVlKCkKCm15X2NvbXBhcmlzb25zIDwtIGxpc3QoIGMoIk5ldXJvbmFsX2NsdXN0ZXIiLCAiQXN0cm9jeXRpY19jbHVzdGVyIiksIAogICAgICAgICAgICAgICAgICAgICAgICBjKCJOZXVyb25hbF9jbHVzdGVyIiwgIk5ldXJvbl92c19Bc3Ryb2N5dGUiKSwgCiAgICAgICAgICAgICAgICAgICAgICAgIGMoIkFzdHJvY3l0aWNfY2x1c3RlciIsICJOZXVyb25fdnNfQXN0cm9jeXRlIikgKQpgYGAKYGBge3IgZmlnLmhlaWdodD02LCBmaWcud2lkdGg9M30KZ2dwbG90KHF1YWRzLCBhZXMoeD1Db21wYXJpc29uLCB5PWBOb2RlIGNvcnJlbGF0aW9uYCwgZmlsbD1Db21wYXJpc29uKSkgKyAKICAgIGdlb21fdmlvbGluKHRyaW09RiwgY29sb3VyID0gImF6dXJlNCIpKyAgCiAgICBzdGF0X2NvbXBhcmVfbWVhbnMoIHZqdXN0PSAtMS4sIGhqdXN0PSAwLCBjb21wYXJpc29ucyA9IG15X2NvbXBhcmlzb25zLCBtZXRob2QgPSAid2lsY294LnRlc3QiLCBwLmFkanVzdC5tZXRob2QgPSAiYm9uZmVycm9uaSIsIGxhYmVsID0gInAuc2lnbmlmIikrCiAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGFscGhhKGMoImRlZXBza3libHVlMyIsICJibHVldmlvbGV0IiwgImJyb3duMiIpLCAuOCkpICsgCiAgIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpLGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIsICBheGlzLnRpdGxlLng9ZWxlbWVudF9ibGFuaygpLAogICAgICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpLCBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDUwLCBoanVzdCA9IDEpKSAgKyAKICAjZ2d0aXRsZSgiTmV1cm9uYWwgc2VsZi1jb3JyZWxhdGlvbnMiKSArIAogIHNjYWxlX3lfY29udGludW91cyhsaW1pdHM9YygtMC43LCAyKSkgKyBnZW9tX2hsaW5lKHlpbnRlcmNlcHQ9MCwgbGluZXR5cGU9ImRhc2hlZCIsIGNvbG9yID0gImRhcmtyZWQiLCAgc2l6ZT0xKS0+IGNvcnJfY29tcGFyaXNvbnMKCmNvcnJfY29tcGFyaXNvbnMKYGBgCgoKYGBge3J9CmxpYnJhcnkoUHJvamVjdGlvbkJhc2VkQ2x1c3RlcmluZykKbGlicmFyeShQQ0F0b29scykKbGlicmFyeShtYWdyaXR0cikKCnAgPC0gcGNhKG15LmNvcnIubWF0KQoKY2JpbmQocCRyb3RhdGVkJFBDMSwgcCRyb3RhdGVkJFBDMikgJT4lIGFzLm1hdHJpeCAlPiUgYXMuZGF0YS5mcmFtZS0+IG15X3BjYQoKCgoKbXlfcGNhICU+JSAgY2JpbmQoIGRhdGEkYE5vZGUgdHlwZWApICU+JSBzZXRfY29sbmFtZXMoYygnUEMxJywnUEMyJywnbm9kZScpKSAtPiB0by5wY2Euc2NhdHRlcgoKc3VtbWFyeXplZC5vcHRpbWFsaXR5IC0+IE9wCnRvdGFsLmFicy5jZW50cmFsaXR5IC0+IENlbnRyYWxpdHkKQ2VudHJhbGl0eSAtPiBDZQoKYiA8LSBnZ3Bsb3QodG8ucGNhLnNjYXR0ZXIsIGFlcyh4ID0gUEMxLCB5ID0gUEMyKSkgCmIgKyAgIHNjYWxlX2NvbG9yX21hbnVhbChsYWJlbHMgPSBjKCJBc3RybyIsICJFeGNoIiwnTmV1JywnU2kvRGUgQXN0JywgJ1NpL0RlIE5ldScpLCB2YWx1ZXMgPSAgYygiZGVlcHNreWJsdWUzIiwnZGFya2dyZWVuJywnYnJvd24yJywnZGFya2dvbGRlbnJvZCcsJ2JsdWV2aW9sZXQnKSkgKyAKICB0aGVtZShsZWdlbmQucG9zaXRpb249InJpZ2h0IiwgbGVnZW5kLmJveCA9ICJ2ZXJ0aWNhbCIpKyBnZW9tX3BvaW50KGFlcyhzaXplID0gT3AsIGNvbG9yID0gbm9kZSkpLT4gcGNhX25vZGVfUmVkQ29zdHMKCgoKCmIgPC0gZ2dwbG90KHRvLnBjYS5zY2F0dGVyLCBhZXMoeCA9IFBDMSwgeSA9IFBDMikpIApiICsgICBzY2FsZV9jb2xvcl9tYW51YWwobGFiZWxzID0gYygiQXN0cm8iLCAiRXhjaCIsJ05ldScsJ1NpL0RlIEFzdCcsICdTaS9EZSBOZXUnKSwgdmFsdWVzID0gIGMoImRlZXBza3libHVlMyIsJ2RhcmtncmVlbicsJ2Jyb3duMicsJ2Rhcmtnb2xkZW5yb2QnLCdibHVldmlvbGV0JykpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb249InJpZ2h0IiwgbGVnZW5kLmJveCA9ICJ2ZXJ0aWNhbCIpKyBnZW9tX3BvaW50KGFlcyggc2l6ZSA9ICBDZSxjb2xvciA9IG5vZGUpKS0+IHBjYV9ub2RlX2NlbnRyYWxpdHkKYGBgCgpgYGB7cn0KCgpib3R0b21fcGFuZWwgPC0gZ2dhcnJhbmdlKCBjb3JyX2NvbXBhcmlzb25zLHBjYV9ub2RlX2NlbnRyYWxpdHksIHBjYV9ub2RlX1JlZENvc3RzICAsIG5jb2wgPSAzLCB3aWR0aHMgID0gYyguNCwxLDEpICwgbGFiZWxzID0gYygnZCcsJ2UnLCdmJyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoanVzdCA9IDAsIHZqdXN0ID0gMSkKCmBgYAoKYGBge3IgZmlnLmhlaWdodD05LCBmaWcud2lkdGg9MTN9CgoKcGFuZWxfd2l0aG91dF9ncmFwaCA8LSAgZ2dhcnJhbmdlKHVwcGVyX3BhbmVsLCBib3R0b21fcGFuZWwsIG5yb3cgPSAyLCBoZWlnaHRzID0gYygxLDAuNSkpCnBhbmVsX3dpdGhvdXRfZ3JhcGgKCmBgYAoKYGBge3J9Cmdnc2F2ZShmaWxlPSJwYW5lbF93aXRob3V0X2dyYXBoLnBuZyIsIHBsb3Q9cGFuZWxfd2l0aG91dF9ncmFwaCwgd2lkdGg9MTMsIGhlaWdodD05LCBkcGkgPSAzMjApCmBgYAoKCk5vZGUgdHlwZXMgZm9yIE5ldHdvcmt4IChweXRob24pIGF0dHJpYnV0ZXMKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkobWFncml0dHIpCgpSZXN1bHRzIDwtIHJlYWRfY3N2KCJSZXN1bHRzLmNzdiIpCgpkYXRhX05ldHdvcmt4IDwtIFJlc3VsdHMlPiUgCiAgICAgICAgIG11dGF0ZShgTm9kZV90eXBlYCA9IGlmZWxzZShzdHJfZGV0ZWN0KEZvcm11bGEsIHJlZ2V4KCdcXFtbYS16XUFcXF0nLCAgICAgICAgICAgICAgaWdub3JlX2Nhc2UgPSBUKSksICdBc3Ryb2N5dGUnLCBOQSkpICU+JSAKICAgICAgICAgbXV0YXRlKGBOb2RlX3R5cGVgID0gaWZlbHNlKHN0cl9kZXRlY3QoRm9ybXVsYSwgcmVnZXgoJ1xcW1thLXpdTlxcXScsICAgICAgICAgICAgICBpZ25vcmVfY2FzZSA9IFQpKSwgJ05ldXJvbicsIGBOb2RlX3R5cGVgKSkgICAgJT4lIAogICAgICAgICBtdXRhdGUoYE5vZGVfdHlwZWAgPSBpZmVsc2UoIHN0cl9kZXRlY3QoRm9ybXVsYSwgcmVnZXgoJ1xcW1thLXpdQVxcXXxcXFtbYS16XU5cXF0nLGlnbm9yZV9jYXNlID0gVCksIG5lZ2F0ZSA9IFQpLCAnRXhjaGFuZ2UnLCBgTm9kZV90eXBlYCkpICU+JQogICAgICAgICBtdXRhdGUoYE5vZGVfdHlwZWAgPSBpZmVsc2Uoc3RyX2RldGVjdChGb3JtdWxhLCByZWdleCgnXFxbW2Etel1BXFxdJyxpZ25vcmVfY2FzZSA9IFQpKSAmIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdHJfZGV0ZWN0KE5hbWUsIHJlZ2V4KCdETV98RGVtYW5kfHNpbmsnLGlnbm9yZV9jYXNlID0gVCkpLCAnU2luay9EZW1hbmQgQXN0cm8nLCBgTm9kZV90eXBlYCkpJT4lCiAgICAgICAgIG11dGF0ZShgTm9kZV90eXBlYCA9IGlmZWxzZShzdHJfZGV0ZWN0KEZvcm11bGEsIHJlZ2V4KCdcXFtbYS16XU5cXF0nLGlnbm9yZV9jYXNlID0gVCkpICYgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0cl9kZXRlY3QoTmFtZSwgcmVnZXgoJ0RNX3xEZW1hbmR8c2luaycsaWdub3JlX2Nhc2UgPSBUKSksICdTaW5rL0RlbWFuZCBOZXVyb24nLCBgTm9kZV90eXBlYCkpCgoKZGF0YV9OZXR3b3JreCAlPD4lIHNlbGVjdChjKElELCBOb2RlX3R5cGUpKQoKd3JpdGVfY3N2KGRhdGFfTmV0d29ya3gsICdkYXRhX05ldHdvcmt4LmNzdicpCgpgYGAKCgoKCgoKCg==